package com.gwtapps.serialization;
import java.io.PrintWriter;
import java.io.Serializable;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.gwtapps.serialization.client.DocumentObject;
import com.gwtapps.serialization.client.ObjectSerializer;


public class SerializationGenerator extends Generator 
{
	private JClassType serializeInterface;
	private JClassType stringClass;
	private SourceWriter srcWriter;
	private String className;
	public String generate(TreeLogger logger, GeneratorContext ctx,
		      String requestedClass) throws UnableToCompleteException 
	{
		//get the type oracle
	    TypeOracle typeOracle = ctx.getTypeOracle();
	    assert (typeOracle != null);
	    serializeInterface = typeOracle.findType(Serializable.class.getName());
	    assert( serializeInterface!= null );
	    stringClass = typeOracle.findType(String.class.getName());
	    assert( stringClass!= null );

	    //get class from type oracle
	    JClassType serializeClass = typeOracle.findType(requestedClass);
	    if (serializeClass == null) {
	      logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"
	        + requestedClass + "'", null);
	      throw new UnableToCompleteException();
	    }
	    
		//create source writer
		String packageName = serializeClass.getPackage().getName();
		className = serializeClass.getSimpleSourceName()+"_TypeSerializer";
		PrintWriter printWriter = ctx.tryCreate(logger, packageName, className);
		if (printWriter == null) {
			return packageName+"."+className;
		}
		ClassSourceFileComposerFactory composerFactory = 
			new ClassSourceFileComposerFactory( packageName, className );
		composerFactory.addImport(DocumentObject.class.getName());
		composerFactory.addImport(ObjectSerializer.class.getName());
		composerFactory.addImport(Serializable.class.getName());
		composerFactory.setSuperclass("Serializer");
		srcWriter = composerFactory.createSourceWriter(ctx, printWriter);
		if (srcWriter == null) {
			return packageName+"."+className;
		}
		
		//create a serializer for each interface that supports Serializable
		JClassType[] subTypes = serializeInterface.getSubtypes();
		for( int i=0; i< subTypes.length; ++i )
		{
			srcWriter.println("public class "+subTypes[i].getName()+"_SerializableImpl implements ObjectSerializer{");
			srcWriter.indent();
			srcWriter.println("public "+subTypes[i].getName()+"_SerializableImpl(){}");
			writeSerialize(logger,subTypes[i]);
			writeDeserialize(logger,subTypes[i]);
			srcWriter.outdent();
			srcWriter.println("}");
		}
		
		
		//in the class constructor, add each serializer
		srcWriter.println("public "+className+"(){");
		srcWriter.indent();
		for( int i=0; i< subTypes.length; ++i )
		{
			srcWriter.println("addObjectSerializer(\""+subTypes[i].getName()+"\", new "+subTypes[i].getName()+"_SerializableImpl() );");
		}
		srcWriter.outdent();
		srcWriter.println("}");
		
		srcWriter.commit(logger);
		return packageName+"."+className;
	}

	public void writeSerialize(TreeLogger logger, JClassType classType) throws UnableToCompleteException 
	{
		String fullName = classType.getQualifiedSourceName();
		srcWriter.println("public void serialize( Serializable obj, DocumentObject document ){");
		srcWriter.indent();
		
		//cast the objec to the concrete class
		srcWriter.println(fullName+" objImpl = ("+fullName+")obj;");
		
		//iterate over the get methods
		JMethod[] methods = classType.getMethods();
		for( int i=0; i < methods.length; i++ ){
			
			String methodName = methods[i].getName();
			if( "get".compareTo( methodName.substring(0,3) ) == 0 && methods[i].isPublic() ){
				
				//call the setAttribute method on the document object
				JType returnType = methods[i].getReturnType();
				if( returnType.isClass() != null  && returnType.isClass().isAssignableTo( stringClass ) ){
					String attributeName = methodName.substring(3);
					srcWriter.println( "document.setAttribute(\""+attributeName.toLowerCase()+"\", objImpl."+methodName+"());");
				}
			}
		}
		srcWriter.outdent();	
		srcWriter.println("}");	
		srcWriter.println();
	}
	
	
	public void writeDeserialize(TreeLogger logger,JClassType classType) throws UnableToCompleteException 
	{
		String fullName = classType.getQualifiedSourceName();
		srcWriter.println("public Serializable deserialize( DocumentObject document ){");
		srcWriter.indent();
		
		//create a new concrete object
		srcWriter.println(fullName+" obj = new "+fullName+"();");
		
		//iterate over the set methods
		JMethod[] methods = classType.getMethods();
		for( int i=0; i < methods.length; i++ ){
			
			String methodName = methods[i].getName();
			if( "set".compareTo( methodName.substring(0,3) ) == 0 && methods[i].isPublic() ){
				
				//make sure there is only one parater
				JParameter[] parameters = methods[i].getParameters();
				if( parameters.length == 1 && 
					parameters[0].getType().isClass() != null &&
					parameters[0].getType().isClass().isAssignableTo( stringClass ) ){
					
					//get the string value of the attribute
					String attributeName = methodName.substring(3);
					srcWriter.println("obj."+methodName+"( document.getAttribute(\""+attributeName.toLowerCase()+"\") );");		
				}
			}
		}
		srcWriter.println("return obj;");
		srcWriter.outdent();	
		srcWriter.println("}");	
		srcWriter.println();
	}
	
}
